//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#include "CBasicFileSystem.h"
#define IncrementDllLockCount() do {} while(0)
#define DecrementDllLockCount() do {} while(0)
#include "_CKtfsFileSystem.cpp"

#include "CBasicDirectory.h"
#include "CBasicFile.h"
#include <stdio.h>
#include <stdlib.h>
#include <fat.h>
#include <misc.h>
#include <fsys.h>
#include <llist.h>
#include <abc.h>
#include "mutex.h"
#include "event.h"

void Ktfs_Init_Lock(IMutex **ppLock)
{
    assert(ppLock);
    *ppLock = new CMutex;
    assert(*ppLock);
    (*ppLock)->AddRef();
}

void Ktfs_Lock(IMutex **ppLock)
{
    WaitResult wr;
    (*ppLock)->Lock(&wr);
}

void Ktfs_Unlock(IMutex **ppLock)
{
    (*ppLock)->Unlock();
}

void Ktfs_Term_Lock(IMutex **ppLock)
{
    (*ppLock)->Release();
}

CKtfsFileSystem::CKtfsFileSystem()
{
    m_pRootIBasicDirector = NULL;
    m_pRootCFatFile = NULL;
    m_pFileSystem = NULL;
    m_pStub = NULL;
    m_pSyncThread = NULL;
}

CKtfsFileSystem::~CKtfsFileSystem()
{
    UnbindDriver();
}

ECode CKtfsFileSystem::Sync()
{
    Ktfs_Lock(&m_pRootCFatFile->f_node->n_lock);
    m_pRootCFatFile->pFatDir->sync();
    Ktfs_Unlock(&m_pRootCFatFile->f_node->n_lock);
    return NOERROR;
}

ECode CKtfsFileSystem::GetRoot(
    /* [out] */ IBasicDirectory * * piRoot)
{
    if (!m_pFileSystem) {
        return E_FS_NO_ROOT;
    }

    if (NULL == piRoot) {
        return E_INVALID_ARGUMENT;
    }

    if (NULL == m_pRootIBasicDirector) {
        return E_FS_NO_ROOT;
    }

    m_pRootIBasicDirector->AddRef();
    *piRoot = m_pRootIBasicDirector;

    return NOERROR;
}

ECode CKtfsFileSystem::Lookup(
    /* [in] */ WString wstrPath,
    /* [out] */ IBasicFile * * piBasicFile)
{
    if (NULL == this->m_pRootIBasicDirector) {
        return E_FS_NO_ROOT;
    }

    return this->m_pRootIBasicDirector->Lookup(wstrPath, piBasicFile);
}

ECode SyncBufRoutine(void *pvArg)
{
    ECode ec = NOERROR;
    WaitResult result;
    EventState state;
    fat_filesystem* pFsSys = (fat_filesystem*)pvArg;
    fatfs_sharedata* pSharedata;
    abc* pAbc;

    pSharedata = pFsSys->p_Sharedata;
    pAbc = pFsSys->pAbc;
    while(!pSharedata->bExit) {
        assert(pSharedata->pSyncBufEvent);
        ec = pSharedata->pSyncBufEvent->Wait(&result, &state);
        if (FAILED(ec) || WaitResult_OK != result || state == SIGNALED) {
            break;
        }
        DzSleep(10, NULL);
        while(!pSharedata->bExit && pAbc->sync_one_block());
    }

    pAbc->Release();
    pSharedata->Release();

    return ec;
}

ECode CKtfsFileSystem::BindDriver(
    /* [in] */ WString wstrDevName)
{
    ECode ec;

    m_pFileSystem = new fat_filesystem(L"ktfs", (wchar_t*)(const wchar_t*)wstrDevName);
    if (m_pFileSystem == NULL) {
        DPRINTF(("ERROR:New File System Object Error\n"));
        return E_OUT_OF_MEMORY;
    }

    if (!m_pFileSystem->NewDataStructure()) {
        DPRINTF(("ERROR:m_pFileSystem->NewDataStructure() failed!\n"));
        ec = E_OUT_OF_MEMORY;
        goto BD_ERR_EXIT;
    }

    if (!m_pFileSystem->OpenBlkDevice()) {
        DPRINTF(("ERROR:OpenBlkDevice\n"));
        ec = E_FS_IO_ERROR;
        goto BD_ERR_EXIT;
    }

    if (!m_pFileSystem->InitFatfs()) {
        DPRINTF(("ERROR:InitFatfs\n"));
        ec = E_FS_IO_ERROR;
        goto BD_ERR_EXIT;
    }

    m_pRootCFatFile = new CFatFile;
    if (NULL == m_pRootCFatFile) {
        DPRINTF(("ERROR:Memory over!\n"));
        ec = E_OUT_OF_MEMORY;
        goto BD_ERR_EXIT;
    }
    m_pRootCFatFile->AddRef();

    m_pRootCFatFile->pFatDir = m_pFileSystem->pFatDir;
    m_pRootCFatFile->pFatDir->AddRef();
    m_pRootCFatFile->f_node = m_pFileSystem->p_Sharedata->procroot;
    m_pRootCFatFile->f_perm = DEFAULT_PERM;

    m_pRootIBasicDirector = new CKtfsDirectory(m_pRootCFatFile);
    if (NULL == m_pRootIBasicDirector) {
        DPRINTF(("ERROR: m_pRootIBasicDirector is NULL!\n"));
        ec = E_OUT_OF_MEMORY;
        goto BD_ERR_EXIT;
    }
    m_pRootIBasicDirector->AddRef();

    m_pFileSystem->p_Sharedata->pSyncBufEvent = new CEvent(FALSE, FALSE);
    if (NULL == m_pFileSystem->p_Sharedata->pSyncBufEvent) {
        DPRINTF(("ERROR: p_Sharedata->pSyncBufEvent is NULL!\n"));
        ec = E_OUT_OF_MEMORY;
        goto BD_ERR_EXIT;
    }
    m_pFileSystem->p_Sharedata->pSyncBufEvent->AddRef();

    m_pFileSystem->p_Sharedata->AddRef();
    m_pFileSystem->pAbc->AddRef();

    ec = DzCreateKernelThread(SyncBufRoutine, m_pFileSystem,
        CreateThreadFlag_System, ThreadPriorityRank_Normal,
        &m_pSyncThread);
    if(FAILED(ec)) {
        m_pFileSystem->pAbc->Release();
        m_pFileSystem->p_Sharedata->Release();
        DPRINTF(("ERROR: Create Thread failed.\n"));
        goto BD_ERR_EXIT;
    }

    m_pFileSystem->used = TRUE;
    return NOERROR;

BD_ERR_EXIT:
    if (m_pFileSystem) {
        delete m_pFileSystem;
        m_pFileSystem = NULL;
    }
    if (m_pRootCFatFile) delete m_pRootCFatFile;
    if (m_pRootIBasicDirector) m_pRootIBasicDirector->Release();

    return ec;
}

ECode CKtfsFileSystem::UnbindDriver()
{
    ECode ec = NOERROR;
    if (m_pRootIBasicDirector) {
        m_pRootIBasicDirector->Release();
        m_pRootIBasicDirector = NULL;
    }

    if (m_pRootCFatFile) {
        m_pRootCFatFile->Release();
        m_pRootCFatFile = NULL;
    }

    if (m_pFileSystem) {
        m_pFileSystem->p_Sharedata->bExit = TRUE;
        m_pFileSystem->p_Sharedata->pSyncBufEvent->Notify(SIGNALED);
        m_pSyncThread->Join(3000, NULL);
        m_pSyncThread->Release();
        m_pSyncThread = NULL;
        delete m_pFileSystem;
        m_pFileSystem = NULL;
    }

    return ec;
}

void CKtfsFileSystem::SetStub(IStub *pStub)
{
    m_pStub = pStub;
}

IStub *CKtfsFileSystem::GetStub()
{
    return m_pStub;
}

ECode CKtfsFileSystem::GetStat(
    /* [out] */ FileSystemStat * pFileSystemStat)
{
    if (NULL == pFileSystemStat)
        return E_INVALID_ARGUMENT;
    pFileSystemStat->clusterSize = m_pFileSystem->p_Sharedata->clsize;
    pFileSystemStat->totalClusters = m_pFileSystem->p_Sharedata->nclust;
    pFileSystemStat->freeClusters =
                    (Int32)(m_pFileSystem->pFat->fat_calcfreespace() \
                    / m_pFileSystem->p_Sharedata->clsize);
    switch (m_pFileSystem->p_Sharedata->fat_size) {
        case 12:
            pFileSystemStat->fatType = FatType_FAT12;
            break;
        case 16:
            pFileSystemStat->fatType = FatType_FAT16;
            break;
        case 32:
            pFileSystemStat->fatType = FatType_FAT32;
            break;
        default:
            pFileSystemStat->fatType = FatType_Unknown;
            break;
    }

    return NOERROR;
}
